package com.onion.orm.support.command;

import com.google.common.base.Preconditions;
import com.onion.utils.support.exception.OnionException;
import com.onion.utils.utils.StringHelper;
import com.onion.orm.support.defination.ExprEntry;
import com.onion.orm.support.filter.QueryData;
import com.onion.orm.support.filter.QueryFilter;
import com.onion.orm.utils.ColumnUtil;
import com.onion.orm.utils.ParamUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

import com.onion.orm.support.command.FieldCommand.*;

@SuppressWarnings("all")
public class DynamicQueryFilter extends QueryFilter implements java.io.Serializable {

    private static final long serialVersionUID = 8790528179232633456L;

    private static final Logger logger = LoggerFactory.getLogger(DynamicQueryFilter.class);

    private String filterName = "";
    private List<Object> paramValues;
    private List<QueryCommand> commands;
    private List<DynamicQueryFilter> subFilters;
    private boolean logic_and = true;

    public DynamicQueryFilter() {
        filterName = "";
        commands = new ArrayList<>();
        paramValues = new ArrayList<>();
        subFilters = new ArrayList<>();
    }

    public <T> DynamicQueryFilter addFilter(String fieldName, Operator operator, T value) {
        Preconditions.checkNotNull(value);
        if (value instanceof String) {
            return addFilter(fieldName, operator, Type.String, (String)value);
        } else if (value instanceof Number) {
            return addFilter(fieldName, operator, Type.Number, (Number)value);
        } else if (value instanceof Date) {
            return addFilter(fieldName, operator, Type.Date, (Date)value);
        } else if (value instanceof Boolean) {
            return addFilter(fieldName, operator, Type.Number, ((Boolean)value).booleanValue() ? 1 : 0);
        } else if (value instanceof ExprEntry) {
            return addFilter(fieldName, operator, Type.Expression, (ExprEntry)value);
        } else {
            throw new RuntimeException("不支持的类型");
        }
    }

    /**
     * @param fieldName
     * @param collection
     * @return
     */
    public DynamicQueryFilter in(String fieldName, Collection<?> collection) {
        if(collection.size() > IN_MAX_SIZE){
            throw new OnionException(-1, "数据库in条件操作，超过最大size【1000】，请调整优化查询条件！");
        }
        return addFilter(fieldName, Operator.in, Type.List, collection);
    }

    /**
     *
     * @param fieldName
     * @param collection
     * @return
     */
    public DynamicQueryFilter notIn(String fieldName, Collection<?> collection) {
        return addFilter(fieldName, Operator.notIn, Type.List, collection);
    }

    /**
     * 针对1,3,4,5,5,6,11,33形式的串进行检索
     * 但注意类似检索用不到索引，非管理后台的功能请注意加缓存，并评估好数据量
     * @param fieldName
     * @param value
     * @return
     */
    public DynamicQueryFilter inJoinedString(String fieldName, String value) {
        return addFilter(fieldName, Operator.inJoinString, Type.String, value);
    }

    /**
     * 表达式支持
     * 因为可能产生注入，所以一般禁止在其中拼接变量
     * @param fieldName
     * @param operator
     * @param expr
     * @return
     */
    public <T> DynamicQueryFilter expr(String fieldName, Operator operator, ExprEntry expr) {
        return addFilter(fieldName, operator, Type.Expression, expr);
    }

    public <T> DynamicQueryFilter equalPair(String fieldName, T value) {
        return addFilter(fieldName, Operator.equal, value);
    }

    public <T> DynamicQueryFilter greatThan(String fieldName, T value) {
        return addFilter(fieldName, Operator.greatThan, value);
    }

    public <T> DynamicQueryFilter lessThan(String fieldName, T value) {
        return addFilter(fieldName, Operator.lessThan, value);
    }

    public <T> DynamicQueryFilter notEqual(String fieldName, T value) {
        return addFilter(fieldName, Operator.notEqual, value);
    }

    public <T> DynamicQueryFilter startsWith(String fieldName, String value) {
        return addFilter(fieldName, Operator.startsWith, value);
    }

    public <T> DynamicQueryFilter endsWith(String fieldName, String value) {
        return addFilter(fieldName, Operator.endsWith, value);
    }

    public <T> DynamicQueryFilter like(String fieldName, String value) {
        return addFilter(fieldName, Operator.like, value);
    }

    public <T> DynamicQueryFilter greatOrEqual(String fieldName, T value) {
        return addFilter(fieldName, Operator.greatOrEqual, value);
    }

    public <T> DynamicQueryFilter lessOrEqual(String fieldName, T value) {
        return addFilter(fieldName, Operator.lessOrEqual, value);
    }

    /**
     * 目前对于数值、字符串、日期、可遍历集合(Collection)、布尔等常见类型做了快捷调用定义
     * 对于更高级的beginOfDay/endOfDay依旧需要调用此全功能方法
     * @param fieldName
     * @param operator
     * @param type
     * @param value
     * @return
     */
    public DynamicQueryFilter addFilter(String fieldName, Operator operator, Type type, Object value) {
        if (operator == Operator.isNull || operator == Operator.isNotNull) {
            FieldCommand fieldCommand = new FieldCommand(fieldName, value, operator, this);
            commands.add(fieldCommand);
            paramValues.add(fieldCommand.getValue());
        } else {
            value = ParamUtil.convertObject(type, value);
            if (value != null) {
                FieldCommand fieldCommand = new FieldCommand(fieldName, value, operator, this);
                commands.add(fieldCommand);
                paramValues.add(fieldCommand.getValue());
            } else {
                throw new RuntimeException("value parsing exception: " + fieldName + " => " + value);
            }
        }
        return this;
    }

    /**
     * 设置条件逻辑关系，默认值为“与”
     * @param val
     */
    public DynamicQueryFilter setLogicAnd(boolean val) {
        logic_and = val;
        return this;
    }

    /**
     * 本Filter条件关系为and
     *
     * @return
     */
    public DynamicQueryFilter and() {
        logic_and = true;
        return this;
    }

    /**
     * 本Filter条件关系为or
     *
     * @return
     */
    public DynamicQueryFilter or() {
        logic_and = false;
        return this;
    }

    /**
     * 添加子过滤器，仅条件有效，排序无效
     * @param filter
     */
    public DynamicQueryFilter addSubFilter(DynamicQueryFilter filter) {
        filter.setFilterName("sub" + subFilters.size());
        subFilters.add(filter);
        return this;
    }

    /**
     * 将本Filter作为目标Filter的子条件集合
     *
     * @param filter
     * @return
     */
    public DynamicQueryFilter addToFilter(DynamicQueryFilter filter) {
        filter.addSubFilter(this);
        return this;
    }

    /**
     * 通用老方法，不推荐使用
     * @deprecated
     */
    public DynamicQueryFilter addFilter(Map<String, String[]> rawParaMap) {

        Set<Map.Entry<String, String[]>> entrySet = rawParaMap.entrySet();
        for (Map.Entry<String, String[]> entry : entrySet) {
            String paraName = entry.getKey();
            String paramValue = entry.getValue()[0];
            if (paraName.startsWith("Q_")) {
                addFilter(paraName, paramValue);
            }
        }

        String[] s_start = rawParaMap.get("start");
        String[] s_limit = rawParaMap.get("limit");
        if (s_start != null && StringUtils.isNotEmpty(s_start[0]))
            this.offset = Integer.valueOf(s_start[0]);
        if (s_limit != null && StringUtils.isNotEmpty(s_limit[0]))
            this.limit = Integer.valueOf(s_limit[0]);
        String[] sortby = rawParaMap.get("sortby");
        String[] order = rawParaMap.get("sort");
        if (sortby != null && order != null && StringUtils.isNotEmpty(sortby[0]) && StringUtils.isNotEmpty(order[0]))
            setSortField(sortby[0], order[0].toLowerCase().equals("desc") ? true : false);
        if (rawParaMap.get("searchAll") != null && "true".equals(rawParaMap.get("searchAll")[0])) {
            offset = limit = 0;
        }
        return this;
    }

    /**
     * @deprecated
     */
    public DynamicQueryFilter addFilter(String paramName, Object paramValue) {
        String fieldInfo[] = paramName.split("_");
        int fieldLength = fieldInfo != null ? fieldInfo.length : 0;
        if (fieldLength == 4) {
            addFilter(fieldInfo[1], Operator.fromString(fieldInfo[3]), Type.fromString(fieldInfo[2]), paramValue);
        } else if (fieldLength == 3) {
            addFilter(fieldInfo[1], Operator.fromString(fieldInfo[2]), Type.String, "");
            paramValues.add("");
        } else if (fieldLength > 4) {
            StringBuilder fieldName = new StringBuilder();
            for (int i = 1; i < fieldLength - 2; i++) {
                if (fieldName.length() > 0) {
                    fieldName.append("_");
                }
                fieldName.append(fieldInfo[i]);
            }
            addFilter(fieldName.toString(), Operator.valueOf(fieldInfo[fieldLength - 1]), Type.valueOf(fieldInfo[fieldLength - 2]), paramValue);
        } else {
            logger.error((new StringBuilder("Query param name [")).append(paramName).append("] is not right format.")
                    .toString());
        }
        return this;
    }

    /**
     * @deprecated in favor of
     * {@link #orderBy(String, boolean)}
     */
    public DynamicQueryFilter setSortField(String orderBy, boolean isDesc) {
        return orderBy(orderBy, isDesc);
    }

    /**
     * 内部名字，内部使用
     * @param filterName
     */
    private void setFilterName(String filterName) {
        this.filterName = filterName;
    }

    public List<Object> getParamValueList() {
        return this.paramValues;
    }

    public QueryData getParameters() {
        return getQueryData();
    }

    /**
     * 组装面向数据库表的SQL
     * @param cmd
     * @param parameterMap
     * @return
     */
    private String getPartSql(FieldCommand cmd, Map<String, Object> parameterMap) {
        String property = cmd.getProperty();
        Operator operation = cmd.getOperation();
        Object value = cmd.getValue();

        String partHql = "";
        //占位符
        String fieldKey = operation + "_" + property + "_" + (filterName != null && filterName.length() > 0 ? (filterName + "_") : "") + parameterMap.size();
        //转下划线字段名
        String fieldName = StringHelper.toUnderline(property);
        //判断是否有字段映射，如果有做转换，本功能其实貌似没什么使用场景，未来可能废弃
        String fieldNameWithTable = (fieldMappings != null && fieldMappings.containsKey(fieldName)) ? fieldMappings.get(fieldName) : fieldName;

        if (value instanceof ExprEntry) {
            ExprEntry expr = (ExprEntry) value;
            //特殊处理表达式类型
            String op = null;
            switch (operation) {
                case lessThan:
                    op = " < ";
                    break;
                case greatThan:
                    op = " > ";
                    break;
                case greatOrEqual:
                    op = " >= ";
                    break;
                case lessOrEqual:
                    op = " <= ";
                    break;
                case like:
                    op = " like ";
                    break;
                case notEqual:
                    op = " <> ";
                    break;
                case equal:
                    op = " = ";
                    break;

                default:
                    throw new RuntimeException("Syntax Invalid:Property=" + property + "\tOperation=" + operation + "\tValue=" +value);
            }
            //对字段名字的处理先尝试做纯属性名处理，如转换失败(@符不消失)则采用expr规则
            String name = null;
            if (property.contains("@")) {
                //特殊处理方式
                name = ColumnUtil.parseColumnNames(null, property, expr.getClz());
            }
            if (name == null) {
                //普通处理方式
                name = fieldNameWithTable;
            }
            return String.format("%s%s%s", name, op,
                    ColumnUtil.parseColumnNames(property, expr.getValue(), expr.getClz()));
        }
        String fieldKeyReplace = String.format("#{%s}", fieldKey);
        switch (operation) {
            case lessThan:
                partHql = (new StringBuilder(String.valueOf(fieldNameWithTable))).append(" <").append(fieldKeyReplace).toString();
                parameterMap.put(fieldKey, value);
                break;
            case greatThan:
                partHql = (new StringBuilder(String.valueOf(fieldNameWithTable))).append(" > ").append(fieldKeyReplace).toString();
                parameterMap.put(fieldKey, value);
                break;
            case greatOrEqual:
                partHql = (new StringBuilder(String.valueOf(fieldNameWithTable))).append(" >= ").append(fieldKeyReplace).toString();
                parameterMap.put(fieldKey, value);
                break;
            case lessOrEqual:
                partHql = (new StringBuilder(String.valueOf(fieldNameWithTable))).append(" <= ").append(fieldKeyReplace).toString();
                parameterMap.put(fieldKey, value);
                break;
            case like:
                partHql = (new StringBuilder(String.valueOf(fieldNameWithTable))).append(" like ").append(fieldKeyReplace).toString();
                parameterMap.put(fieldKey, "%" + value + "%");
                break;
            case startsWith:
                partHql = (new StringBuilder(String.valueOf(fieldNameWithTable))).append(" like ").append(fieldKeyReplace).toString();
                parameterMap.put(fieldKey, value + "%");
                break;
            case endsWith:
                partHql = (new StringBuilder(String.valueOf(fieldNameWithTable))).append(" like ").append(fieldKeyReplace).toString();
                parameterMap.put(fieldKey, "%" + value);
                break;
            case isNull:
                partHql = (new StringBuilder(String.valueOf(fieldNameWithTable))).append(" is null ").toString();
                break;
            case isNotNull:
                partHql = (new StringBuilder(String.valueOf(fieldNameWithTable))).append(" is not null ").toString();
                break;
            case inJoinString:
                partHql = String.format("concat(',', `%s`, ',') like %s", fieldNameWithTable, fieldKeyReplace);
                parameterMap.put(fieldKey, "%," + value + ",%");
                break;
            case in:
            case notIn: {
                List<String> valueList = ParamUtil.getCollection(value);
                StringBuilder sb = new StringBuilder(String.valueOf(fieldNameWithTable))
                        .append(operation == Operator.notIn ? " not  " : " ").append("in")
                        .append(" ( ");
                for (int i = 0; i < valueList.size(); i++) {
                    if (i != 0) {
                        sb.append(",");
                    }
                    sb.append("#{").append(fieldKey).append("_").append(i).append("}");
                    parameterMap.put(fieldKey + "_" + i, valueList.get(i));
                }
                partHql = sb.append(" )").toString();
                break;
            }
            case notEqual:
                partHql = (new StringBuilder(String.valueOf(fieldNameWithTable))).append(" !=").append(fieldKeyReplace).toString();
                parameterMap.put(fieldKey, value);
                break;
            case equal:
                partHql = (new StringBuilder(String.valueOf(partHql))).append(fieldNameWithTable).append(" =")
                        .append(fieldKeyReplace).toString();
                parameterMap.put(fieldKey, value);
                break;
            case lessOrEqualTimestamp:
                partHql = fieldNameWithTable + " <= FROM_UNIXTIME(" + fieldKeyReplace+")";
                parameterMap.put(fieldKey, value);
                break;
            case greatOrEqualTimestamp:
                partHql = fieldNameWithTable + " >= FROM_UNIXTIME(" + fieldKeyReplace+")";
                parameterMap.put(fieldKey, value);
                break;

            default:
                throw new RuntimeException("Syntax Invalid:Property=" + property + "\tOperation=" + operation + "\tValue=" +value);
        }
        return partHql;
    }

    @Override
    public QueryData getQueryData() {
        QueryData map = new QueryData();
        QueryCommand cmd = null;
        StringBuffer where = new StringBuffer("");
        String orderBy = null;
        for (int i = 0; i < commands.size(); i++) {
            cmd = commands.get(i);
            if (cmd instanceof FieldCommand) {
                if (where.length() > 0) {
                    if (logic_and) {
                        where.append(" and ");
                    } else {
                        where.append(" or ");
                    }
                }
                where.append(getPartSql((FieldCommand) cmd, map));
            } else if (cmd instanceof SortCommand) {
                if (orderBy == null) {
                    orderBy = ((SortCommand) cmd).getPartSql();
                } else {
                    orderBy += ((SortCommand) cmd).getPartSql2();
                }
            }
        }

        //处理子filter，只支持where解析
        for (DynamicQueryFilter sub : subFilters) {
            if (where.length() > 0) {
                if (logic_and) {
                    where.append(" and ");
                } else {
                    where.append(" or ");
                }
            }
            where.append("(");
            Map<String, Object> params = sub.getParameters();
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                if (entry.getKey().equals("SortSQL")) {
                    //do nothing
                } else if (entry.getKey().equals("WhereSQL")) {
                    where.append(entry.getValue());
                } else {
                    //field
                    map.put(entry.getKey(), entry.getValue());
                }
            }
            where.append(")");
        }
        map.put("SearchAll", false);
        if (where.length() < 1) {
            where.append("1=1");
            map.put("SearchAll", true);
        }

        map.put("WhereSQL", where.toString());
        map.put("SortSQL", orderBy);
        map.setLimit(offset, limit);

        return map;
    }

    @Override
    public DynamicQueryFilter orderBy(String orderBy, boolean isDesc) {
        commands.add(new SortCommand(orderBy, isDesc));
        return this;
    }

}